﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace DiscoveryLogic.Analytics.Diagrams.Physics.Renderers
{
	public class DrawingVector : ICloneable
	{
		#region Private members and properties

		private double m_X;

		/// <summary>
		/// X Coordination of vector
		/// </summary>
		public double X
		{
			get { return m_X; }
			set { m_X = value; }
		}

		private double m_Y;

		/// <summary>
		/// Y Coordination of vector
		/// </summary>
		public double Y
		{
			get { return m_Y; }
			set { m_Y = value; }
		}

		private double m_Z;

		/// <summary>
		/// Z Coordination of vector
		/// </summary>
		public double Z
		{
			get { return m_Z; }
			set { m_Z = value; }
		}

		/// <summary>
		/// Gets the length of vector.
		/// </summary>
		/// <value>The length.</value>
		public double Length
		{
			get { return (double)Math.Sqrt(X * X + Y * Y + Z * Z); }
		}

		/// <summary>
		/// Gets the angle (in radiands) between x-axis and vector's projection to OXY plane.
		/// </summary>
		/// <value>The angle.</value>
		public double Alpha
		{
			get { return (double)Math.Atan2(Y, X); }
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Default constructor. Initiate vector at the (0,0,0) location
		/// </summary>
		public DrawingVector()
		{
		}

		/// <summary>
		/// Initiate 2D vector with given parameters
		/// </summary>
		/// <param name="inX">X coordination of vector</param>
		/// <param name="inY">Y coordination of vector</param>
		public DrawingVector(double inX, double inY)
		{
			m_X = inX;
			m_Y = inY;
			m_Z = 0;
		}

		/// <summary>
		/// Initiate vector with given parameters
		/// </summary>
		/// <param name="inX">X coordination of vector</param>
		/// <param name="inY">Y coordination of vector</param>
		/// <param name="inZ">Z coordination of vector</param>
		public DrawingVector(double inX, double inY, double inZ)
		{
			m_X = inX;
			m_Y = inY;
			m_Z = inZ;
		}

		/// <summary>
		/// Initiate vector with given parameters
		/// </summary>
		/// <param name="coordination">Vector's coordinations as an array</param>
		public DrawingVector(double[] coordination)
		{
			m_X = coordination[0];
			m_Y = coordination[1];
			m_Z = coordination[2];
		}

		/// <summary>
		/// Initiate vector with same values as given Vector
		/// </summary>
		/// <param name="vector">Vector to copy coordinations</param>
		public DrawingVector(DrawingVector vector)
		{
			m_X = vector.X;
			m_Y = vector.Y;
			m_Z = vector.Z;
		}

		#endregion

		#region Methods

		/// <summary>
		/// Add 2 vectors and create a new one.
		/// </summary>
		/// <param name="vector1">First vector</param>
		/// <param name="vector2">Second vector</param>
		/// <returns>New vector that is the sum of the 2 vectors</returns>
		public static DrawingVector Add(DrawingVector vector1, DrawingVector vector2)
		{
			if (((Object)vector1 == null) || ((Object)vector2 == null))
				return null;
			return new DrawingVector(vector1.X + vector2.X, vector1.Y + vector2.Y, vector1.Z + vector2.Z);
		}

		/// <summary>
		/// Substract 2 vectors and create a new one.
		/// </summary>
		/// <param name="vector1">First vector</param>
		/// <param name="vector2">Second vector</param>
		/// <returns>New vector that is the difference of the 2 vectors</returns>
		public static DrawingVector Subtract(DrawingVector vector1, DrawingVector vector2)
		{
			if (((Object)vector1 == null) || ((Object)vector2 == null))
				return null;
			return new DrawingVector(vector1.X - vector2.X, vector1.Y - vector2.Y, vector1.Z - vector2.Z);
		}

		/// <summary>
		/// Return a new DrawingVector with negative values.
		/// </summary>
		/// <param name="vector">Original vector</param>
		/// <returns>New vector that is the inversion of the original vector</returns>
		public static DrawingVector Negate(DrawingVector vector)
		{
			if ((Object)vector == null) return null;
			return new DrawingVector(-vector.X, -vector.Y, -vector.Z);
		}

		/// <summary>
		/// Multiply a vector with a scalar
		/// </summary>
		/// <param name="vector">Vector to be multiplied</param>
		/// <param name="val">Scalar to multiply vector</param>
		/// <returns>New vector that is the multiplication of the vector with the scalar</returns>
		public static DrawingVector Multiply(DrawingVector vector, double val)
		{
			if ((Object)vector == null)
				return null;
			return new DrawingVector(vector.X * val, vector.Y * val, vector.Z * val);
		}

		/// <summary>
		/// Calculates dot product of n vectors.
		/// </summary>
		/// <param name="vectors">vectors array.</param>
		/// <returns></returns>
		public static double DotProduct(params DrawingVector[] vectors)
		{
			if (vectors.Length < 2)
				throw new ArgumentException("dot product can be calculated from at least two vectors");
			double dx = vectors[0].X, dy = vectors[0].Y, dz = vectors[0].Z;

			for (int i = 1; i < vectors.Length; i++)
			{
				dx *= vectors[0].X;
				dy *= vectors[0].Y;
				dz *= vectors[0].Z;
			}

			return (dx + dy + dz);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="vect"></param>
		/// <param name="dLength"></param>
		/// <returns></returns>
		public static DrawingVector Contract(DrawingVector vect, double dLength)
		{
			double length = vect.Length;
			if (length == 0) throw new ArgumentException("Vector length equals zero. Can't contract or expand.");
			return new DrawingVector(vect.X - (vect.X * dLength / length),
							  vect.Y - (vect.Y * dLength / length),
							  vect.Z - (vect.Z * dLength / length));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="vect"></param>
		/// <param name="dLength"></param>
		/// <returns></returns>
		public static DrawingVector Expand(DrawingVector vect, double dLength)
		{
			return Contract(vect, -1 * dLength);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="dx"></param>
		/// <param name="dy"></param>
		/// <param name="dz"></param>
		public void Translate(double dx, double dy, double dz)
		{
			X += dx;
			Y += dy;
			Z += dz;
		}

		#endregion

		#region Operators

		/// <summary>
		/// Check equality of two vectors
		/// </summary>
		/// <param name="vector1">First vector</param>
		/// <param name="vector2">Second vector</param>
		/// <returns>True - if he 2 vectors are equal.
		/// False - otherwise</returns>
		public static bool operator ==(DrawingVector vector1, DrawingVector vector2)
		{
			if (((Object)vector1 == null) || ((Object)vector2 == null)) return false;
			return ((vector1.X.Equals(vector2.X))
					&& (vector1.Y.Equals(vector2.Y))
					&& (vector1.Z.Equals(vector2.Z)));
		}

		/// <summary>
		/// Check inequality of two vectors
		/// </summary>
		/// <param name="vector1">First vector</param>
		/// <param name="vector2">Second vector</param>
		/// <returns>True - if he 2 vectors are not equal.
		/// False - otherwise</returns>
		public static bool operator !=(DrawingVector vector1, DrawingVector vector2)
		{
			if (((Object)vector1 == null) || ((Object)vector2 == null)) return false;
			return (!(vector1 == vector2));
		}

		/// <summary>
		/// Calculate the sum of 2 vectors.
		/// </summary>
		/// <param name="vector1">First vector</param>
		/// <param name="vector2">Second vector</param>
		/// <returns>New vector that is the sum of the 2 vectors</returns>
		public static DrawingVector operator +(DrawingVector vector1, DrawingVector vector2)
		{
			if (((Object)vector1 == null) || ((Object)vector2 == null)) return null;
			return Add(vector1, vector2);
		}

		/// <summary>
		/// Calculate the substraction of 2 vectors
		/// </summary>
		/// <param name="vector1">First vector</param>
		/// <param name="vector2">Second vector</param>
		/// <returns>New vector that is the difference of the 2 vectors</returns>
		public static DrawingVector operator -(DrawingVector vector1, DrawingVector vector2)
		{
			if (((Object)vector1 == null) || ((Object)vector2 == null))
				return null;
			return Subtract(vector1, vector2);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="vector1"></param>
		/// <param name="dLength"></param>
		/// <returns></returns>
		public static DrawingVector operator -(DrawingVector vector1, double dLength)
		{
			if ((Object)vector1 == null) return null;
			return Contract(vector1, dLength);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="vector1"></param>
		/// <param name="dLength"></param>
		/// <returns></returns>
		public static DrawingVector operator +(DrawingVector vector1, double dLength)
		{
			if ((Object)vector1 == null) return null;
			return Expand(vector1, dLength);
		}

		/// <summary>
		/// Calculate the negative (inverted) vector
		/// </summary>
		/// <param name="vector">Original vector</param>
		/// <returns>New vector that is the invertion of the original vector</returns>
		public static DrawingVector operator -(DrawingVector vector)
		{
			if ((Object)vector == null) return null;
			return Negate(vector);
		}

		/// <summary>
		/// Calculate the multiplication of a vector with a scalar
		/// </summary>
		/// <param name="vector">Vector to be multiplied</param>
		/// <param name="val">Scalar to multiply vector</param>
		/// <returns>New vector that is the multiplication of the vector and the scalar</returns>
		public static DrawingVector operator *(DrawingVector vector, double val)
		{
			if ((Object)vector == null) return null;
			return Multiply(vector, val);
		}

		/// <summary>
		/// Calculate the multiplication of a vector with a scalar
		/// </summary>
		/// <param name="val">Scalar to multiply vector</param>
		/// <param name="vector">Vector to be multiplied</param>
		/// <returns>New vector that is the multiplication of the vector and the scalar</returns>
		public static DrawingVector operator *(double val, DrawingVector vector)
		{
			if ((Object)vector == null) return null;
			return Multiply(vector, val);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public double this[byte index]
		{
			get
			{
				if (index < 0 || index > 2) throw new ArgumentException("index has to be integer from interval [0, 2]");
				switch (index)
				{
					case 0:
						return X;
					case 1:
						return Y;
					case 2:
						return Z;
					default:
						return 0;
				}
			}
			set
			{
				if (index < 0 || index > 2) throw new ArgumentException("index has to be integer from interval [0, 2]");
				switch (index)
				{
					case 0:
						X = value;
						break;
					case 1:
						Y = value;
						break;
					case 2:
						Z = value;
						break;
					default:
						break;
				}
			}
		}

		#endregion

		#region Constants

		/// <summary>
		/// Standard (0,0,0) vector
		/// </summary>
		public static DrawingVector Zero
		{
			get { return new DrawingVector(0.0f, 0.0f, 0.0f); }
		}

		/// <summary>
		/// Standard (1,0,0) vector
		/// </summary>
		public static DrawingVector XAxis
		{
			get { return new DrawingVector(1.0f, 0.0f, 0.0f); }
		}

		/// <summary>
		/// Standard (0,1,0) vector
		/// </summary>
		public static DrawingVector YAxis
		{
			get { return new DrawingVector(0.0f, 1.0f, 0.0f); }
		}

		/// <summary>
		/// Standard (0,0,1) vector
		/// </summary>
		public static DrawingVector ZAxis
		{
			get { return new DrawingVector(0.0f, 0.0f, 1.0f); }
		}

		#endregion

		#region Overides

		/// <summary>
		/// Determines whether the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>.
		/// </summary>
		/// <param name="obj">The <see cref="T:System.Object"></see> to compare with the current <see cref="T:System.Object"></see>.</param>
		/// <returns>
		/// true if the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>; otherwise, false.
		/// </returns>
		public override bool Equals(object obj)
		{
			return (obj is DrawingVector && (DrawingVector)obj == this);
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
		/// </returns>
		public override string ToString()
		{
			return string.Format(CultureInfo.InvariantCulture, "({0}, {1}, {2})", m_X, m_Y, m_Z);
		}

		/// <summary>
		/// Serves as a hash function for a particular type. <see cref="M:System.Object.GetHashCode"></see> is suitable for use in hashing algorithms and data structures like a hash table.
		/// </summary>
		/// <returns>
		/// A hash code for the current <see cref="T:System.Object"></see>.
		/// </returns>
		public override int GetHashCode()
		{
			return m_X.GetHashCode() ^ m_Y.GetHashCode() ^ m_Z.GetHashCode();
		}

		#endregion

		#region ICloneable Members

		public object Clone()
		{
			return new DrawingVector(this);
		}

		#endregion
	}
}
